package com.myutil.utils.http;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

/**
 * 通用http发送方法
 * 
 * 
 */
public class HttpUtils {
	private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

	/**
	 * 向指定 URL 发送GET方法的请求
	 *
	 * @param url   发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendGet(String url) {
		return sendGet(url,null);
	}
	public static String sendGet(String url, String param) {
		return sendGet(url, param, null, null, null, null);
	}

	/**
	 * 向指定 URL 发送GET方法的请求
	 *
	 * @param url         发送请求的 URL
	 * @param param       请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
	 * @param contentType 编码类型
	 * @return 所代表远程资源的响应结果
	 */
	public static String  UTF8="utf-8";
	public static JSONObject sendGet(String url, JSONObject param, Map<String, Object> headers, Integer connectTimeout,
			Integer readTimeout) {
		String result = sendGet(url, param == null ? null : param.toJSONString(), headers, UTF8,
				connectTimeout, readTimeout);
		return result == null ? null : JSON.parseObject(result);
	}

	public static String sendGet(String url, String urlParam, Map<String, Object> headers, String charset,
			Integer connectTimeout, Integer readTimeout) {// String contentType
		if (!StringUtils.isEmpty(urlParam)) {
			url = url + "?" + urlParam;
		}
		return sendAndGetString("GET", url, null, headers, charset, connectTimeout, readTimeout);
	}

	/*@SuppressWarnings("unchecked")
	public static JSONObject request(String method,String url, String bodyParam, Map<String, Object> headers, String charset,
			Integer connectTimeout, Integer readTimeout) {
		JSONObject ret = new JSONObject();
		JSONObject fillResponseHeaders = new JSONObject();
		String result = sendAndGetString( method, url,  bodyParam, headers, charset,connectTimeout, readTimeout,fillResponseHeaders);
		Integer HTTP_RESPONSE_CODE = Integer.valueOf(fillResponseHeaders.getInteger("HTTP_RESPONSE_CODE"));
		ret.put("status", HTTP_RESPONSE_CODE);
		ret.put("headers", fillResponseHeaders);
		if(!StringUtils.isEmpty(result)) {
			JSON obj= (JSON) JSON.parse(result);
			ret.put("body", obj);
		}
		return ret;
	}*/

	public static String sendAndGetString(String method, String url, String bodyParam, Map<String, Object> headers,
			String charset, Integer connectTimeout, Integer readTimeout) {
		InputStream is = null;
		String result = null;
		try {
			is = send(method, url, bodyParam, headers, charset, connectTimeout, readTimeout);
			result = IOUtils.toString(is,
					StringUtils.isEmpty(charset) ? Charset.forName("UTF-8") : Charset.forName(charset));
			/*in = new BufferedReader(new InputStreamReader(is, charset));// "UTF-8"
			String line;
			while ((line = in.readLine()) != null) {
				result.append(line);
			}*/
			log.info("recv - {}", result);
		} catch (Exception e) {
			log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",bodyParam=" + bodyParam, e);
			throw new RuntimeException(e);
		} finally {
			try {
				if (is != null) {
					is.close();
				}
			} catch (Exception ex) {
				log.error("调用in.close Exception, url=" + url + ",bodyParam=" + bodyParam, ex);
			}
		}
		return result;
	}

	/*public static String sendPost(String url, String param,Map<String,Object> headers,String charset,Integer connectTimeout,Integer readTimeout) {
		PrintWriter out = null;
		BufferedReader in = null;
		StringBuilder result = new StringBuilder();
		try {
			String urlNameString = url;
			log.info("sendPost - {}", urlNameString);
			
			
			URL realUrl = new URL(urlNameString);
			HttpURLConnection conn = (HttpURLConnection)realUrl.openConnection();
			
			if(url.startsWith("https://")) {
				HttpsURLConnection conns = (HttpsURLConnection)conn;
	
				SSLContext sc = SSLContext.getInstance("SSL");
				sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
				
				conns.setSSLSocketFactory(sc.getSocketFactory());
				conns.setHostnameVerifier(new TrustAnyHostnameVerifier());
			}
			
			fillHeader(conn,headers);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			
			if (connectTimeout == null) {
				connectTimeout = 3000;
			}
			conn.setConnectTimeout(connectTimeout);
			if (readTimeout == null) {
				readTimeout = 3000;
			}
			conn.setReadTimeout(readTimeout);
			if (charset == null) {
				charset = Constants.UTF8;
			}
			
			conn.connect();
			
			out = new PrintWriter(conn.getOutputStream());
			out.print(param);
			out.flush();
			in = new BufferedReader(new InputStreamReader(conn.getInputStream(), charset));
			String line;
			while ((line = in.readLine()) != null) {
				result.append(line);
			}
			
			
			Integer code = conn.getResponseCode();
			if (code == 302) {
				// 如果会重定向，保存302重定向地址，以及Cookies,然后重新发送请求(模拟请求)
				String location = conn.getHeaderField("Location");
				conn.disconnect();
				conn = (HttpURLConnection) new URL(location).openConnection();
				conn.setConnectTimeout(connectTimeout);
				conn.setReadTimeout(readTimeout);
				System.out.println("302 location:"+location);
				return sendPost(location, param,headers,charset,connectTimeout,readTimeout) ;
			}
			
			if (code != HttpURLConnection.HTTP_OK) {
				throw new RuntimeException("HTTP_STATUS " + code);
			}
			log.info("recv - {}", result);
		}catch (Exception e) {
			log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
			throw new RuntimeException(e);
		} finally {
			try {
				if (out != null) {
					out.close();
				}
				if (in != null) {
					in.close();
				}
			} catch (IOException ex) {
				log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
			}
		}
		return result.toString();
	}*/

	public static String URLEncode(Map<String, Object> data, String charset) throws UnsupportedEncodingException {
		if (data != null && !data.isEmpty()) {
			StringBuffer str = new StringBuffer();
			for (Entry<String, Object> entry : data.entrySet()) {
				String key = entry.getKey();
				String value = entry.getValue().toString();
				if (str.length() > 0) {
					str.append("&");
				}
				str.append(URLEncoder.encode(key, charset) + "=" + URLEncoder.encode(value, charset));
			}
			return str.toString();
		}
		return null;
	}

	public static Object parseStringToJsonOrOtherType(String content) {
		Object response = null;
		if (content.startsWith("{") && content.endsWith("}") || content.startsWith("[") && content.endsWith("]")) {
			response = (JSON) JSON.parse(content);
		} else if (content.startsWith("\"")) {
			response = content.substring(1, content.length() - 1);
			response = ((String) response).replaceAll("\\\\\"", "\"");
		} else if (content.equals("true") || content.equals("false")) {
			response = new Boolean(content);
		} else if (content.matches("\\-?\\d+")) {
			Long value = Long.valueOf(content);
			if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
				response = value.intValue();
			} else {
				response = value;
			}
		} else if (content.matches("\\-?\\d+\\.\\d+")) {
			response = new BigDecimal(content);
		} else if (content.equals("null")) {
			response = null;
		} else {
			response = content;
		}
		return response;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static JSONObject requestJsonApi(String method, String url, JSONObject queryParam, Object bodyParam,
			Map<String, Object> headers, String charset, Integer connectTimeout, Integer readTimeout, String proxyHost,
			Integer proxyPort) {
		Charset defaultCharset = StringUtils.isEmpty(charset) ? Charset.forName("UTF-8") : Charset.forName(charset);
		InputStream is = null;
		try {
			int qIndex = url.indexOf("?");
			if (qIndex > -1) {
				String[] urlArr = url.split("\\?");
				if (urlArr.length > 1) {
					if (queryParam == null) {
						queryParam = new JSONObject();
					}
					String[] keyValues = urlArr[1].split("&");
					for (int i = 0; i < keyValues.length; i++) {
						String[] kv = keyValues[i].split("=");

						String key = kv[0];
						String value = kv[1];
						if (!queryParam.containsKey(key)) {
							queryParam.put(key, value);
						}
					}
					url = urlArr[0];
				}
			}

			if (queryParam != null && !queryParam.isEmpty()) {
				String str = URLEncode(queryParam, defaultCharset.name());
				if (!url.endsWith("?")) {
					url += "?" + str;
				}
			}

			String bodyParamStr = null;
			String contentType = headers == null ? null : (String) headers.get("Content-Type");// content-type
			// Content-Type
			if (bodyParam != null) {
				if (bodyParam instanceof Map) {
					if (!StringUtils.isEmpty(contentType)) {
						String[] contentTypeArr = contentType.split(";");
						if (contentTypeArr[0].equals("application/x-www-form-urlencoded")) {
							Charset urlEncodeCharset = defaultCharset;
							if (contentTypeArr.length > 1) {
								if (contentTypeArr.length > 1) {
									Map<String, String> contentTypeProp = new HashMap<>();
									for (int i = 1; i < contentTypeArr.length; i++) {
										String[] kv = contentTypeArr[i].split("=");
										contentTypeProp.put(kv[0], kv[1]);
									}
									String contentTypeCharset = contentTypeProp.get("charset");
									if (!StringUtils.isEmpty(contentTypeCharset)) {
										// charset=UTF-8
										urlEncodeCharset = Charset.forName(contentTypeCharset);
									}
								}

							}
							bodyParamStr = URLEncode((Map) bodyParam, urlEncodeCharset.name());
							// x-www-form-urlencoded;charset=UTF-8
						} else if (contentTypeArr[0].equals("application/xml")) {
							bodyParamStr = JSON.toJSONString(bodyParam);
						} else if (contentTypeArr[0].startsWith("text/")) {
							// bodyParamStr = String.valueOf(((Map)bodyParam).get("text"));
							bodyParamStr = JSON.toJSONString(bodyParam);
						} else if (contentTypeArr[0].equals("multipart/form-data")) {
							// 暂不支持
						}
					} else {
						bodyParamStr = JSON.toJSONString(bodyParam);
					}
				} else if (bodyParam instanceof String) {
					bodyParamStr = (String) bodyParam;
				}
			}

			Response res = request(method, url, bodyParamStr, headers, charset, connectTimeout, readTimeout, proxyHost,
					proxyPort);
			is = res.getInputStream();
			Object responseContent = null;
			if (is != null) {

				String result = IOUtils.toString(is, defaultCharset);
				try {
					// responseContent = (JSON) JSON.parse(result);
					responseContent = parseStringToJsonOrOtherType(result);
				} catch (Exception ex) {
					responseContent = result;
				}
			}

			JSONObject ret = new JSONObject();
			ret.put("status", res.getHttpResponseCode());
			Map<String, String> responseHeaders = new LinkedHashMap<>();
			for (Entry<String, List<String>> entry : res.getHeaders().entrySet()) {
				String key = entry.getKey();
				String value = StringUtils.join(entry.getValue(), ",");
				if (key != null) {
					responseHeaders.put(key, value);
				}
			}
			ret.put("headers", responseHeaders);
			ret.put("body", responseContent);
			return ret;
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (is != null) {
					is.close();
				}
			} catch (Exception ex) {
				log.error("requestJsonApi 异常", ex);
			}
		}
	}

	/*static {
		allowMethods("PATCH");
	}
	
	private static void allowMethods(String... methods) {
		try {
			Field methodsField = HttpURLConnection.class.getDeclaredField("methods");
			Field modifiersField = Field.class.getDeclaredField("modifiers");
			modifiersField.setAccessible(true);
			modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL);
	
			methodsField.setAccessible(true);
	
			String[] oldMethods = (String[]) methodsField.get(null);
			Set<String> methodsSet = new LinkedHashSet<>(Arrays.asList(oldMethods));
			methodsSet.addAll(Arrays.asList(methods));
			String[] newMethods = methodsSet.toArray(new String[0]);
	
			methodsField.set(nullstatic field, newMethods);
		} catch (NoSuchFieldException | IllegalAccessException e) {
			throw new IllegalStateException(e);
		}
	}*/

	public static Response request(String method, String url, String bodyParam, Map<String, Object> headers,
			String charset, Integer connectTimeout, Integer readTimeout, String proxyHost, Integer proxyPort)
			throws IOException, NoSuchAlgorithmException, KeyManagementException {// String contentType
		method = StringUtils.isEmpty(method) ? "GET" : method.toUpperCase();
		// String urlNameString = url + (urlParam == null ? "" : "?" + urlParam);
		log.info("send{} - {}", method, url);
		URL realUrl = new URL(url);
		HttpURLConnection conn = null;
		if (!StringUtils.isEmpty(proxyHost) && proxyPort != null) {
			Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));// "localhost",7890
			conn = (HttpURLConnection) realUrl.openConnection(proxy);
		} else {
			conn = (HttpURLConnection) realUrl.openConnection();
		}
		/*if("PATCH".equals(method)) {
			allowMethods("PATCH");
		}*/

		conn.setRequestMethod(method);// GET POST
		if (url.startsWith("https://")) {
			HttpsURLConnection conns = (HttpsURLConnection) conn;
			SSLContext sc = SSLContext.getInstance("SSL");
			sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
			conns.setSSLSocketFactory(sc.getSocketFactory());
			conns.setHostnameVerifier(new TrustAnyHostnameVerifier());

		}

		// 解决HttpsURLConnection不支持PATCH的问题
		if (method.equalsIgnoreCase("PATCH")) {
			conn.setRequestMethod("POST");
			// 此标头可用于 POST 请求以“伪造”其他 HTTP 方法。只需将 X-HTTP-Method-Override 标头的值设置为您要实际执行的 HTTP
			// 方法。所以使用下面的代码。
			conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
		}

		fillHeader(conn, headers);
		if (!StringUtils.isEmpty(bodyParam)) {
			conn.setDoOutput(true);
		}
		conn.setDoInput(true);

		if (connectTimeout == null) {
			connectTimeout = 3000;
		}
		conn.setConnectTimeout(connectTimeout);
		if (readTimeout == null) {
			readTimeout = 3000;
		}
		conn.setReadTimeout(readTimeout);
		if (charset == null) {
			charset = UTF8;
		}

		conn.connect();
		if (!StringUtils.isEmpty(bodyParam)) {
			PrintWriter out = new PrintWriter(conn.getOutputStream());
			out.print(bodyParam);
			out.flush();
			out.close();
		}

		Integer code = conn.getResponseCode();
		if (code == 301 || code == 302) {
			// 如果会重定向，保存302重定向地址，以及Cookies,然后重新发送请求(模拟请求)
			String location = conn.getHeaderField("Location");
			/*conn.disconnect();
			conn = (HttpURLConnection) new URL(location).openConnection();
			conn.setConnectTimeout(connectTimeout);
			conn.setReadTimeout(readTimeout);*/
			System.out.println("302 location:" + location);
			return request(method, location, bodyParam, headers, charset, connectTimeout, readTimeout, proxyHost,
					proxyPort);
		} else {
			InputStream is = null;
			try {
				is = conn.getInputStream();
			} catch (Exception ex) {
				is = conn.getErrorStream();
				// log.error("conn.getInputStream() Exception:",ex);
			}
			return new Response(code, conn.getHeaderFields(), is);
		}
	}

	public static InputStream send(String method, String url, String bodyParam, Map<String, Object> headers,
			String charset, Integer connectTimeout, Integer readTimeout)
			throws IOException, NoSuchAlgorithmException, KeyManagementException {
		Response response = request(method, url, bodyParam, headers, charset, connectTimeout, readTimeout, null, null);
		Integer code = response.getHttpResponseCode();
		if (code < 200 || code >= 300) {
			throw new RuntimeException("HTTP_STATUS " + code);
		}
		return response.getInputStream();
	}

	/**
	 * 向指定 URL 发送POST方法的请求
	 *
	 * @param url   发送请求的 URL
	 * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
	 * @return 所代表远程资源的响应结果
	 */
	public static JSON sendPost(String url, JSONObject body, Map<String, Object> headers, Integer connectTimeout,
			Integer readTimeout) {
		String result = sendAndGetString("POST", url, body == null ? null : body.toJSONString(), headers,
				UTF8, connectTimeout, readTimeout);
		/*String result = sendPost(url, body == null ? null : body.toJSONString(), headers, Constants.UTF8,
				connectTimeout, readTimeout);*/
		return result == null ? null : JSON.parseObject(result);
	}

	private static void fillHeader(URLConnection conn, Map<String, Object> headers) {
		conn.setRequestProperty("accept", "*/*");
		conn.setRequestProperty("connection", "Keep-Alive");
		conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
		conn.setRequestProperty("Accept-Charset", "utf-8");
		conn.setRequestProperty("contentType", "utf-8");

		if (headers != null) {
			for (Map.Entry<String, Object> entry : headers.entrySet()) {
				conn.setRequestProperty(entry.getKey(), String.valueOf(entry.getValue()));
			}
		}
	}

	public static String sendSSLPost(String url, String param) {
		StringBuilder result = new StringBuilder();
		String urlNameString = url + "?" + param;
		try {
			log.info("sendSSLPost - {}", urlNameString);

			URL console = new URL(urlNameString);
			HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
			conn.setRequestProperty("accept", "*/*");
			conn.setRequestProperty("connection", "Keep-Alive");
			conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
			conn.setRequestProperty("Accept-Charset", "utf-8");
			conn.setRequestProperty("contentType", "utf-8");
			conn.setDoOutput(true);
			conn.setDoInput(true);

			SSLContext sc = SSLContext.getInstance("SSL");
			sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
			conn.setSSLSocketFactory(sc.getSocketFactory());
			conn.setHostnameVerifier(new TrustAnyHostnameVerifier());

			conn.connect();
			InputStream is = conn.getInputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			String ret = "";
			while ((ret = br.readLine()) != null) {
				if (ret != null && !"".equals(ret.trim())) {
					result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8"));
				}
			}
			log.info("recv - {}", result);
			conn.disconnect();
			br.close();
		} catch (ConnectException e) {
			log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
		} catch (SocketTimeoutException e) {
			log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
		} catch (IOException e) {
			log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
		} catch (Exception e) {
			log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
		}
		return result.toString();
	}

	private static class TrustAnyTrustManager implements X509TrustManager {
		@Override
		public void checkClientTrusted(X509Certificate[] chain, String authType) {
		}

		@Override
		public void checkServerTrusted(X509Certificate[] chain, String authType) {
		}

		@Override
		public X509Certificate[] getAcceptedIssuers() {
			return new X509Certificate[] {};
		}
	}

	private static class TrustAnyHostnameVerifier implements HostnameVerifier {
		@Override
		public boolean verify(String hostname, SSLSession session) {
			return true;
		}
	}

	public static void main(String[] args) {
		// sendGet("https://www.baidu.com", null,null,"UTF-8", null, null);
		sendGet("http://www.facebook.com", null, null, "UTF-8", null, null);
	}
}